﻿#pragma once

#include <memory>
#include <vector>
#include <string>
#include <stdexcept>
#include <set>
#include <map>
#include <list>
#include <unordered_set>
#include <unordered_map>
#include <fstream>
#include <mutex>

#include <glm/glm/glm.hpp>
#define GLM_ENABLE_EXPERIMENTAL 1
#include <glm/glm/gtx/quaternion.hpp>
#include <unibmp/unibmp.hpp>

namespace GLBBuilderLib
{
	using namespace glm;
	using TextureImage = UniformBitmap::Image_RGBA8;

	class AddNodeException : public std::invalid_argument
	{
	public:
		AddNodeException(const std::string& what) noexcept;
	};

	class SaveGLBException : public std::ios::failure
	{
	public:
		SaveGLBException(const std::string& what) noexcept;
	};

	struct GLBMeshSubsetData
	{
		std::string Material;
		std::vector<int> TriangleIndices;

		GLBMeshSubsetData() = default;
		GLBMeshSubsetData(const GLBMeshSubsetData& c) = default;
		bool operator==(const GLBMeshSubsetData& c) const = default;
	};

	struct GLBMeshData
	{
		std::string Name;
		std::vector<vec3> Positions;
		std::vector<vec2> TexCoords;
		std::vector<vec3> Normals;
		std::vector<GLBMeshSubsetData> Subsets;

		GLBMeshData() = default;
		GLBMeshData(const GLBMeshData& c) = default;
		bool operator==(const GLBMeshData& c) const = default;

		GLBMeshData(const std::string& comp_name);
	};

	enum class GLBAnimInterpolationType
	{
		LINEAR,
		STEP,
		CUBICSPLINE // 动画信息必须包含 in-tangent, a spline vertex, and an out-tangent
	};

	enum class GLBTextureMimeType
	{
		JPEG = 0,
		PNG = 1
	};

	class GLBAnimMovement
	{
	public:
		// 会跟着移动的 Node
		std::unordered_set<size_t> Nodes;

		// 时间线
		std::vector<float> TimeLine;

		// 位移
		std::vector<vec3> Translations;

		// 旋转
		std::vector<quat> Rotations;

		// 缩放
		std::vector<vec3> Scales;

		// 以上所有 Vector 的 size() 必须相同，或者也可为零如果无需提供。
		void ValidateData() const;
	};

	using RawBinaryType = std::vector<uint8_t>;

	class base64
	{
	public:
		static std::string encode(const RawBinaryType& in);
		static RawBinaryType decode(const std::string& in);
	};

	class GLBBuilder
	{
	public:
		RawBinaryType Buffer;

		bool Verbose = false;

	protected:
		std::unordered_map<std::string, size_t> MaterialIndexer;
		std::map<RawBinaryType, size_t> TextureIndexer;

		struct BufferHash
		{
			size_t operator()(const RawBinaryType& Buf) const;
		};

		struct BufferPosition
		{
			size_t byteOffset;
			size_t byteLength;
		};

		struct BufferViewInfo
		{
			size_t buffer;
			size_t byteLength;
			size_t byteOffset;
			int target;
			size_t byteStride;

			bool operator == (const BufferViewInfo& c) const = default;
			bool operator < (const BufferViewInfo& c) const;

			struct Hash
			{
				size_t operator()(const BufferViewInfo& BV) const;
			};
		};

		struct AccessorInfo
		{
			ptrdiff_t bufferView;
			int componentType;
			size_t count;
			std::string type;

			bool operator == (const AccessorInfo& c) const = default;
			bool operator < (const AccessorInfo& c) const;

			struct Hash
			{
				size_t operator()(const AccessorInfo& a) const;
			};
		};

		std::map<std::vector<uint8_t>, BufferPosition> BufferIndexer;
		std::unordered_map<BufferViewInfo, ptrdiff_t, BufferViewInfo::Hash> BufferViewIndexer;
		std::unordered_map<AccessorInfo, ptrdiff_t, AccessorInfo::Hash> AccessorIndexer;

		// 是否复用缓冲区
		static constexpr bool ReuseBuffers = true;
		int ArraysReusage = 0;
		int ArraysTotal = 0;
		int BufferViewsReusage = 0;
		int BufferViewsTotal = 0;
		int AccessorsReusage = 0;
		int AccessorsTotal = 0;

		// 是否根据缓冲区内的数值范围来使用体积更小的缓冲区格式
		static constexpr bool DoCompressBuffersByRange = true;
		std::map<std::string, size_t> BuffersCompressionStats;

		using JsonExtrasSectionType = std::map<std::string, std::string>;
		using JsonExtrasType = std::map<std::string, JsonExtrasSectionType>;
		static std::string JsonExtrasSectionToString(const JsonExtrasSectionType& SectionMap);
		static std::string JsonExtrasToString(const JsonExtrasType& ExtrasMap);

		std::vector<std::string> JsonAnimations;
		std::vector<std::string> JsonBufferViews;
		std::vector<std::string> JsonMeshes;
		std::vector<std::string> JsonAccessors;
		std::vector<std::string> JsonNodes;
		std::vector<std::string> JsonMaterials;
		std::vector<std::string> JsonTextures;
		std::vector<std::string> JsonImages;
		std::vector<std::string> JsonAsset;
		std::vector<std::string> JsonScenes;
		std::vector<std::string> JsonSamplers;
		JsonExtrasType JsonExtras; // 相当于 UserData 之类的数据

		// q() 使用双引号扩起字符串
		inline static constexpr std::string q(const std::string& s)
		{
			return std::string("\"") + s + "\"";
		}

		// k_v(只有k) 生成双引号括起来的 key ，然后追加冒号，但是值不在这里追加
		inline static constexpr std::string k_v(const std::string& k)
		{
			return q(k) + ": ";
		}

		// k_v(k,v) 生成双引号括起来的 key ，追加冒号，再生成双引号括起来的字符串 value
		inline static constexpr std::string k_v(const std::string& k, const std::string& v)
		{
			return q(k) + ": " + q(v);
		}

		static std::string JsonFieldsToString(const std::vector<std::string>& JsonFields);
		template<typename ContainerType>
		static std::string JsonArrayToString(const ContainerType& Data);

		template<typename T>
		void AddVectorToBuffer(const std::vector<T>& ToAdd, BufferPosition& AddedTo);

		std::shared_ptr<std::mutex> AddBufferViewMutex = std::make_shared<std::mutex>();
		std::shared_ptr<std::mutex> AddAccessorMutex = std::make_shared<std::mutex>();

		template<typename T>
		ptrdiff_t AddBufferView(const std::vector<T>& ToAdd, int Target = 0, size_t byteStride = 0);
		
		ptrdiff_t AddAccessor(const AccessorInfo& a, const std::string& min_ = "", const std::string & max_ = "");

		template<int L> // 各种维度数的向量
		ptrdiff_t AddAccessorVectors(const std::vector<vec<L, float>>& ToAdd);

		template<typename T> requires std::is_same_v<T, int32_t> || std::is_same_v<T, uint32_t>
		ptrdiff_t AddAccessorIntegers(const std::vector<T>& ToAdd, int Target, bool KeepSize);
		template<typename T> requires std::is_same_v<T, int32_t> || std::is_same_v<T, uint32_t>
		ptrdiff_t AddAccessorIndices(const std::vector<T>& ToAdd, bool KeepSize);

		ptrdiff_t AddAccessorIntegers(const std::vector<int8_t>& ToAdd, int Target);
		ptrdiff_t AddAccessorIntegers(const std::vector<uint8_t>& ToAdd, int Target);
		ptrdiff_t AddAccessorIntegers(const std::vector<int16_t>& ToAdd, int Target, bool KeepSize);
		ptrdiff_t AddAccessorIntegers(const std::vector<uint16_t>& ToAdd, int Target, bool KeepSize);
		ptrdiff_t AddAccessorIndices(const std::vector<int8_t>& ToAdd);
		ptrdiff_t AddAccessorIndices(const std::vector<uint8_t>& ToAdd);
		ptrdiff_t AddAccessorIndices(const std::vector<int16_t>& ToAdd, bool KeepSize);
		ptrdiff_t AddAccessorIndices(const std::vector<uint16_t>& ToAdd, bool KeepSize);

		ptrdiff_t AddAccessorFloats(const std::vector<float>& ToAdd, bool KeepSize);
		ptrdiff_t AddAccessorQuats(const std::vector<quat>& ToAdd);

		static bool IsPng(const RawBinaryType& data);
		static bool IsJpeg(const RawBinaryType& data);
		static bool IsTarga(const RawBinaryType& data);

		static bool IsPng(const std::string& filename, const RawBinaryType& data);
		static bool IsJpeg(const std::string& filename, const RawBinaryType& data);
		static bool IsTarga(const std::string& filename, const RawBinaryType& data);

		size_t AddImage(size_t Idx_BufferView, const std::string& mimeType);
		size_t AddTexture(size_t Idx_Image);
		size_t AddTexture
		(
			const TextureImage& Texture,
			bool HasOpacity,
			int JpegQuality = 100
		);
		size_t AddTexture
		(
			const std::string& TexturePath, // 材质路径，用于区分材质
			const RawBinaryType& TextureBinary,
			bool HasOpacity
		);

		std::string MakeMaterialPBRMetallicRoughnessJSON
		(
			const vec4* baseColorFactor, // 基础颜色
			ptrdiff_t Idx_baseColorTexture, // 基础颜色纹理
			double metallicFactor, // 金属度值
			double roughnessFactor, // 粗糙度值
			ptrdiff_t Idx_metalnessRoughnessTexture // 金属度 + 粗糙度纹理
		);

		std::string MakeMaterialNormalTextureJSON
		(
			ptrdiff_t Idx_NormalTexture, // 法线图（bump 图）
			double scale // 缩放
		);

		std::string MakeMaterialOcclusionTextureJSON
		(
			ptrdiff_t Idx_OcclusionTexture, // 环境光遮蔽纹理（灰度）
			double strength // 强度值
		);

		std::string MakeMaterialEmissiveTextureJSON
		(
			ptrdiff_t Idx_EmissiveTexture // 自发光（荧光）材质
		);

		std::string BuffersJSON(bool B64Buffer, const std::string& binSaveTo) const;

	protected:
		using MutexType = std::mutex;
		using ScopedLock = std::scoped_lock<MutexType>;
		
	private:
		std::shared_ptr<MutexType> AddMaterialLock = std::make_shared<MutexType>();
		std::shared_ptr<MutexType> AddAnimationLock = std::make_shared<MutexType>();

	public:
		GLBBuilder();
		GLBBuilder(bool Verbose);

		size_t AddMaterial
		(
			const std::string& MaterialName,
			const vec4& BaseColor = vec4(1, 1, 1, 1),
			double metalnessFactor = 1,
			double roughnessFactor = 1,
			ptrdiff_t Idx_Albedo_Texture = -1,
			ptrdiff_t Idx_Normal_Texture = -1,
			ptrdiff_t Idx_Emissive_Texture = -1,
			ptrdiff_t Idx_Occlusion_Texture = -1,
			ptrdiff_t Idx_MetallicRoughness_Texture = -1
		);

		ptrdiff_t AddMesh(const GLBMeshData& mesh);
		template<typename ArrayType> size_t AddNode(ptrdiff_t MeshIndex, const ArrayType& ChildNodes, const vec3& Translation, const quat& Rotation, const vec3& Scale, const std::string& name);
		template<typename ArrayType> size_t AddNode(ptrdiff_t MeshIndex, const ArrayType& ChildNodes, const mat4& Matrix, const std::string& name);
		size_t AddScene(const std::string& name, const std::unordered_set<size_t>& nodes);

		// 添加动画
		// 所有的 vector 的元素个数必须相同。
		// 使用指针的部分提供 nullptr 表示没有对应信息。
		template<typename ArrayType>
		size_t AddAnimation
		(
			const ArrayType& Movements,
			const std::string& name = "", // 动画名字，为空时没有名字
			GLBAnimInterpolationType LerpType = GLBAnimInterpolationType::LINEAR // 关键帧之间的过渡方式
		);

		// 汇报统计数据
		void ReportStatistics() const;

		std::string GetJSONStr(bool B64Buffer, const std::string& binSaveTo) const;
		void SaveGLTF(const std::string& JsonSaveTo, const std::string& binSaveTo) const;

		std::vector<uint8_t> GenGLBFileBytes(bool B64Buffer) const;
		void SaveGLB(const std::string& SaveTo, bool B64Buffer) const;

		template<typename T> requires std::is_arithmetic_v<T> || std::is_same_v<T, std::string>
		void SetExtrasData(const std::string& SectionName, const std::string& KeyName, const T& Value);

		template<typename CofT>
		void SetExtrasDataArray(const std::string& SectionName, const std::string& KeyName, const CofT& Value);

		void SetExtrasDataRaw(const std::string& SectionName, const std::string& KeyName, const std::string& Value);
	};

	extern template std::string GLBBuilder::JsonArrayToString(const std::vector<std::string>& JsonArray);
	extern template std::string GLBBuilder::JsonArrayToString(const std::unordered_set<size_t>& JsonArray);

	extern template ptrdiff_t GLBBuilder::AddAccessorIntegers(const std::vector<int32_t>& ToAdd, int Target, bool KeepSize);
	extern template ptrdiff_t GLBBuilder::AddAccessorIntegers(const std::vector<uint32_t>& ToAdd, int Target, bool KeepSize);

	extern template ptrdiff_t GLBBuilder::AddAccessorIndices(const std::vector<int32_t>& ToAdd, bool KeepSize);
	extern template ptrdiff_t GLBBuilder::AddAccessorIndices(const std::vector<uint32_t>& ToAdd, bool KeepSize);

	extern template size_t GLBBuilder::AddNode(ptrdiff_t MeshIndex, const std::set<size_t>& ChildNodes, const vec3& Translation, const quat& Rotation, const vec3& Scale, const std::string& name);
	extern template size_t GLBBuilder::AddNode(ptrdiff_t MeshIndex, const std::set<size_t>& ChildNodes, const mat4& Matrix, const std::string& name);
	extern template size_t GLBBuilder::AddNode(ptrdiff_t MeshIndex, const std::unordered_set<size_t>& ChildNodes, const vec3& Translation, const quat& Rotation, const vec3& Scale, const std::string& name);
	extern template size_t GLBBuilder::AddNode(ptrdiff_t MeshIndex, const std::unordered_set<size_t>& ChildNodes, const mat4& Matrix, const std::string& name);
	extern template size_t GLBBuilder::AddNode(ptrdiff_t MeshIndex, const std::vector<size_t>& ChildNodes, const vec3& Translation, const quat& Rotation, const vec3& Scale, const std::string& name);
	extern template size_t GLBBuilder::AddNode(ptrdiff_t MeshIndex, const std::vector<size_t>& ChildNodes, const mat4& Matrix, const std::string& name);
	extern template size_t GLBBuilder::AddNode(ptrdiff_t MeshIndex, const std::list<size_t>& ChildNodes, const vec3& Translation, const quat& Rotation, const vec3& Scale, const std::string& name);
	extern template size_t GLBBuilder::AddNode(ptrdiff_t MeshIndex, const std::list<size_t>& ChildNodes, const mat4& Matrix, const std::string& name);

	extern template size_t GLBBuilder::AddAnimation(const std::list<GLBAnimMovement>& Movements, const std::string& name, GLBAnimInterpolationType LerpType);
	extern template size_t GLBBuilder::AddAnimation(const std::set<GLBAnimMovement>& Movements, const std::string& name, GLBAnimInterpolationType LerpType);
	extern template size_t GLBBuilder::AddAnimation(const std::unordered_set<GLBAnimMovement>& Movements, const std::string& name, GLBAnimInterpolationType LerpType);
	extern template size_t GLBBuilder::AddAnimation(const std::vector<GLBAnimMovement>& Movements, const std::string& name, GLBAnimInterpolationType LerpType);

	extern template void GLBBuilder::SetExtrasData(const std::string& SectionName, const std::string& KeyName, const bool& Value);
	extern template void GLBBuilder::SetExtrasData(const std::string& SectionName, const std::string& KeyName, const int8_t& Value);
	extern template void GLBBuilder::SetExtrasData(const std::string& SectionName, const std::string& KeyName, const int16_t& Value);
	extern template void GLBBuilder::SetExtrasData(const std::string& SectionName, const std::string& KeyName, const int32_t& Value);
	extern template void GLBBuilder::SetExtrasData(const std::string& SectionName, const std::string& KeyName, const int64_t& Value);
	extern template void GLBBuilder::SetExtrasData(const std::string& SectionName, const std::string& KeyName, const uint8_t& Value);
	extern template void GLBBuilder::SetExtrasData(const std::string& SectionName, const std::string& KeyName, const uint16_t& Value);
	extern template void GLBBuilder::SetExtrasData(const std::string& SectionName, const std::string& KeyName, const uint32_t& Value);
	extern template void GLBBuilder::SetExtrasData(const std::string& SectionName, const std::string& KeyName, const uint64_t& Value);
	extern template void GLBBuilder::SetExtrasData(const std::string& SectionName, const std::string& KeyName, const float& Value);
	extern template void GLBBuilder::SetExtrasData(const std::string& SectionName, const std::string& KeyName, const double& Value);
	extern template void GLBBuilder::SetExtrasData(const std::string& SectionName, const std::string& KeyName, const std::string& Value);

	extern template void GLBBuilder::SetExtrasDataArray(const std::string& SectionName, const std::string& KeyName, const std::list<bool>& Value);
	extern template void GLBBuilder::SetExtrasDataArray(const std::string& SectionName, const std::string& KeyName, const std::list<int8_t>& Value);
	extern template void GLBBuilder::SetExtrasDataArray(const std::string& SectionName, const std::string& KeyName, const std::list<int16_t>& Value);
	extern template void GLBBuilder::SetExtrasDataArray(const std::string& SectionName, const std::string& KeyName, const std::list<int32_t>& Value);
	extern template void GLBBuilder::SetExtrasDataArray(const std::string& SectionName, const std::string& KeyName, const std::list<int64_t>& Value);
	extern template void GLBBuilder::SetExtrasDataArray(const std::string& SectionName, const std::string& KeyName, const std::list<uint8_t>& Value);
	extern template void GLBBuilder::SetExtrasDataArray(const std::string& SectionName, const std::string& KeyName, const std::list<uint16_t>& Value);
	extern template void GLBBuilder::SetExtrasDataArray(const std::string& SectionName, const std::string& KeyName, const std::list<uint32_t>& Value);
	extern template void GLBBuilder::SetExtrasDataArray(const std::string& SectionName, const std::string& KeyName, const std::list<uint64_t>& Value);
	extern template void GLBBuilder::SetExtrasDataArray(const std::string& SectionName, const std::string& KeyName, const std::list<float>& Value);
	extern template void GLBBuilder::SetExtrasDataArray(const std::string& SectionName, const std::string& KeyName, const std::list<double>& Value);
	extern template void GLBBuilder::SetExtrasDataArray(const std::string& SectionName, const std::string& KeyName, const std::list<std::string>& Value);

	extern template void GLBBuilder::SetExtrasDataArray(const std::string& SectionName, const std::string& KeyName, const std::vector<bool>& Value);
	extern template void GLBBuilder::SetExtrasDataArray(const std::string& SectionName, const std::string& KeyName, const std::vector<int8_t>& Value);
	extern template void GLBBuilder::SetExtrasDataArray(const std::string& SectionName, const std::string& KeyName, const std::vector<int16_t>& Value);
	extern template void GLBBuilder::SetExtrasDataArray(const std::string& SectionName, const std::string& KeyName, const std::vector<int32_t>& Value);
	extern template void GLBBuilder::SetExtrasDataArray(const std::string& SectionName, const std::string& KeyName, const std::vector<int64_t>& Value);
	extern template void GLBBuilder::SetExtrasDataArray(const std::string& SectionName, const std::string& KeyName, const std::vector<uint8_t>& Value);
	extern template void GLBBuilder::SetExtrasDataArray(const std::string& SectionName, const std::string& KeyName, const std::vector<uint16_t>& Value);
	extern template void GLBBuilder::SetExtrasDataArray(const std::string& SectionName, const std::string& KeyName, const std::vector<uint32_t>& Value);
	extern template void GLBBuilder::SetExtrasDataArray(const std::string& SectionName, const std::string& KeyName, const std::vector<uint64_t>& Value);
	extern template void GLBBuilder::SetExtrasDataArray(const std::string& SectionName, const std::string& KeyName, const std::vector<float>& Value);
	extern template void GLBBuilder::SetExtrasDataArray(const std::string& SectionName, const std::string& KeyName, const std::vector<double>& Value);
	extern template void GLBBuilder::SetExtrasDataArray(const std::string& SectionName, const std::string& KeyName, const std::vector<std::string>& Value);

	extern template void GLBBuilder::SetExtrasDataArray(const std::string& SectionName, const std::string& KeyName, const std::set<bool>& Value);
	extern template void GLBBuilder::SetExtrasDataArray(const std::string& SectionName, const std::string& KeyName, const std::set<int8_t>& Value);
	extern template void GLBBuilder::SetExtrasDataArray(const std::string& SectionName, const std::string& KeyName, const std::set<int16_t>& Value);
	extern template void GLBBuilder::SetExtrasDataArray(const std::string& SectionName, const std::string& KeyName, const std::set<int32_t>& Value);
	extern template void GLBBuilder::SetExtrasDataArray(const std::string& SectionName, const std::string& KeyName, const std::set<int64_t>& Value);
	extern template void GLBBuilder::SetExtrasDataArray(const std::string& SectionName, const std::string& KeyName, const std::set<uint8_t>& Value);
	extern template void GLBBuilder::SetExtrasDataArray(const std::string& SectionName, const std::string& KeyName, const std::set<uint16_t>& Value);
	extern template void GLBBuilder::SetExtrasDataArray(const std::string& SectionName, const std::string& KeyName, const std::set<uint32_t>& Value);
	extern template void GLBBuilder::SetExtrasDataArray(const std::string& SectionName, const std::string& KeyName, const std::set<uint64_t>& Value);
	extern template void GLBBuilder::SetExtrasDataArray(const std::string& SectionName, const std::string& KeyName, const std::set<float>& Value);
	extern template void GLBBuilder::SetExtrasDataArray(const std::string& SectionName, const std::string& KeyName, const std::set<double>& Value);
	extern template void GLBBuilder::SetExtrasDataArray(const std::string& SectionName, const std::string& KeyName, const std::set<std::string>& Value);

	extern template void GLBBuilder::SetExtrasDataArray(const std::string& SectionName, const std::string& KeyName, const std::unordered_set<bool>& Value);
	extern template void GLBBuilder::SetExtrasDataArray(const std::string& SectionName, const std::string& KeyName, const std::unordered_set<int8_t>& Value);
	extern template void GLBBuilder::SetExtrasDataArray(const std::string& SectionName, const std::string& KeyName, const std::unordered_set<int16_t>& Value);
	extern template void GLBBuilder::SetExtrasDataArray(const std::string& SectionName, const std::string& KeyName, const std::unordered_set<int32_t>& Value);
	extern template void GLBBuilder::SetExtrasDataArray(const std::string& SectionName, const std::string& KeyName, const std::unordered_set<int64_t>& Value);
	extern template void GLBBuilder::SetExtrasDataArray(const std::string& SectionName, const std::string& KeyName, const std::unordered_set<uint8_t>& Value);
	extern template void GLBBuilder::SetExtrasDataArray(const std::string& SectionName, const std::string& KeyName, const std::unordered_set<uint16_t>& Value);
	extern template void GLBBuilder::SetExtrasDataArray(const std::string& SectionName, const std::string& KeyName, const std::unordered_set<uint32_t>& Value);
	extern template void GLBBuilder::SetExtrasDataArray(const std::string& SectionName, const std::string& KeyName, const std::unordered_set<uint64_t>& Value);
	extern template void GLBBuilder::SetExtrasDataArray(const std::string& SectionName, const std::string& KeyName, const std::unordered_set<float>& Value);
	extern template void GLBBuilder::SetExtrasDataArray(const std::string& SectionName, const std::string& KeyName, const std::unordered_set<double>& Value);
	extern template void GLBBuilder::SetExtrasDataArray(const std::string& SectionName, const std::string& KeyName, const std::unordered_set<std::string>& Value);
}